<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>发布—订阅模式</title>
  </head>

  <body>
    <h1>
      发布—订阅模式又叫观察者模式，它定义对象间的一种一对多的依赖关系，当一个对象的状
      态发生改变时，所有依赖于它的对象都将得到通知。在 JavaScript 开发中，我们一般用事件模型 来替代传统的发布—订阅模式。
    </h1>
    <script>
      // 发布—订阅模式，也就是常说的观察者模式
      // 现在看看如何一步步实现发布—订阅模式。
      //  首先要指定好谁充当发布者（比如售楼处）；
      //  然后给发布者添加一个缓存列表，用于存放回调函数以便通知订阅者（售楼处的花名册）；
      //  最后发布消息的时候，发布者会遍历这个缓存列表，依次触发里面存放的订阅者回调函数（遍历花名册，挨个发短信）。

      const event = {
        clientList: {},
        listen: function (key, fn) {
          if (!this.clientList[key]) {
            this.clientList[key] = [];
          }
          this.clientList[key].push(fn); // 订阅的消息添加进缓存列表
        },
        trigger: function (...args) {
          const key = args.shift();
          fns = this.clientList[key];
          if (!fns || !fns.length) {
            // 如果没有绑定对应的消息
            return false;
          }
          for (let i = 0, fn; (fn = fns[i++]); ) {
            fn.apply(this, args); // (2) // arguments 是 trigger 时带上的参数
          }
        },
        remove: function (key, fn) {
          const fns = this.clientList[key];
          if (!fns) {
            // 如果 key 对应的消息没有被人订阅，则直接返回
            return false;
          }
          if (!fn) {
            // 如果没有传入具体的回调函数，表示需要取消 key 对应消息的所有订阅
            fns && (fns.length = 0);
          } else {
            for (let l = fns.length - 1; l >= 0; l--) {
              // 反向遍历订阅的回调函数列表
              const _fn = fns[l];
              if (_fn === fn) {
                fns.splice(l, 1); // 删除订阅者的回调函数
              }
            }
          }
        },
      };

      class Event {
        constructor() {
          this.list = {};
        }
        listen(key, fn) {
          if (!this.list[key]) {
            this.list[key] = [];
          }
          this.list[key].push(fn);
        }
        // 发布消息
        trigger() {
          const key = Array.prototype.shift.call(arguments);
          const fns = this.list[key];

          if (!fns) return;
          fns.map((item) => {
            item.apply(this, arguments);
          });
        }
        // 取消订阅方法
        remove(key, fn) {
          const fns = this.list[key];
          if (!fns) {
            return;
          }
          fns.forEach((item, index) => {
            if (item === fn) {
              fns.shift(index, 1);
            }
          });
        }
      }
    </script>
  </body>
</html>
